package logger

import (
	"fmt"
	"gitee.com/wuzheng0709/backend-gopkg/infrastructure/config"
	"gitee.com/wuzheng0709/backend-gopkg/infrastructure/connector/kafka"
	"github.com/IBM/sarama"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
	"io"
	"os"
	"strings"
	"time"
)

// 日志并发执行
//var LogPushSls, _ = ants.NewPool(5000, ants.WithPreAlloc(true))

type Logger struct {
	Config   *Config
	zapSugar *zap.SugaredLogger
}

func New(conf *Config) *Logger {
	if conf == nil {
		conf = defaultConfig()
		//config.EnableFileOut()
		conf.DisableFileOut() // 关闭日志文件输出
	}
	logger := &Logger{
		Config: conf,
	}

	// 根据环境设置 输出格式
	logger.Config.jsonFormat = true
	logger.Config.consoleOut = true
	// 设置 logger
	logger.WithConfig(config.C.Debug, true)

	return logger
}

func (l *Logger) getFileWriter() (infoWriter io.Writer, warnWriter io.Writer) {
	// 获取 info、warn日志文件的io.Writer 抽象 getWriter() 在下方实现
	loggerPath := l.Config.fileOut.path
	appName := l.Config.name

	if loggerPath == "" || strings.EqualFold(l.Config.envMode, "dev") {
		address, _ := os.Getwd()
		loggerPath = address + "/logs"
	}

	// 检查文件夹是否存在
	isExist := CheckFileAndCreate(loggerPath)

	if isExist != nil {
		panic("[文件夹不存在] " + loggerPath)
	}

	warnWriterFileName := loggerPath + "/common-error.log"
	if len(appName) > 0 {
		warnWriterFileName = loggerPath + "/" + appName + "-common-error.log"
	}

	infoWriter = getWriter(loggerPath+"/"+appName+".log",
		l.Config.fileOut.rotationTime,
		l.Config.fileOut.maxAge)

	warnWriter = getWriter(warnWriterFileName,
		l.Config.fileOut.rotationTime,
		l.Config.fileOut.maxAge)

	return
}

func (l *Logger) generateEncoderConfig() zapcore.EncoderConfig {
	// 自定义时间输出格式
	customTimeEncoder := func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
		enc.AppendString(t.Format(logTmFmtWithMS))
	}
	//// 自定义日志级别显示
	//customLevelEncoder := func(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
	//	var levelStr = "[" + level.CapitalString() + "]"
	//	if l.Config.colorful {
	//		switch level.CapitalString() {
	//		case "INFO":
	//			levelStr = Green + levelStr + Reset
	//			break
	//		case "WARN", "DEBUG":
	//			levelStr = Magenta + levelStr + Reset
	//			break
	//		case "ERROR", "PANIC":
	//			levelStr = Red + levelStr + Reset
	//			break
	//		}
	//	}
	//	enc.AppendString(levelStr)
	//}

	//// 自定义文件：行号输出项
	//encodeCaller := zapcore.FullCallerEncoder
	//if !strings.EqualFold(l.Config.envMode, "prod") {
	//	encodeCaller = zapcore.ShortCallerEncoder // 只显示 package/file.go:line
	//}

	// 设置一些基本日志格式 具体含义还比较好理解，直接看zap源码也不难懂
	return zapcore.EncoderConfig{
		MessageKey:     "msg",
		LevelKey:       "level",
		TimeKey:        "ts",
		CallerKey:      "file",
		FunctionKey:    zapcore.OmitKey,
		NameKey:        "logger",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,      // 默认换行符"\n"
		EncodeLevel:    zapcore.LowercaseLevelEncoder,  // 日志等级序列为小写字符串，如:InfoLevel被序列化为 "info"
		EncodeTime:     customTimeEncoder,              // 日志时间格式显示
		EncodeDuration: zapcore.SecondsDurationEncoder, // 时间序列化，Duration为经过的浮点秒数
		EncodeCaller:   zapcore.ShortCallerEncoder,     // 日志行号显示
		EncodeName:     zapcore.FullNameEncoder,
	}
}

type LogKafka struct {
	Topic     string
	Producer  sarama.SyncProducer
	Partition int32
}

func (lk *LogKafka) Write(p []byte) (n int, err error) {
	// 构建消息
	var envName string
	if config.C.Debug {
		envName = "测试环境"
	} else {
		envName = "生产环境"
	}
	msg := &sarama.ProducerMessage{
		Key:       sarama.StringEncoder(config.C.Service.Name + "_" + envName),
		Topic:     lk.Topic,
		Value:     sarama.ByteEncoder(p),
		Partition: lk.Partition,
	}
	// 发现消息
	_, _, err = lk.Producer.SendMessage(msg)
	if err != nil {
		return
	}

	return
}

// 根据配置文件更新 logger
func (l *Logger) WithConfig(isDebug bool, kafkaSwitch bool) {
	var (
		err     error
		allCore []zapcore.Core
	)

	// 生成 encoderConfig
	encoderConfig := l.generateEncoderConfig()

	var encoder zapcore.Encoder
	if !l.Config.jsonFormat {
		encoder = zapcore.NewConsoleEncoder(encoderConfig)
	} else {
		encoder = zapcore.NewJSONEncoder(encoderConfig)
	}

	// 设置级别
	logLevel := convertLevel(l.Config.level)

	// 实现两个判断日志等级的interface
	infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl < zapcore.WarnLevel && lvl >= logLevel
	})

	warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl >= zapcore.WarnLevel && lvl >= logLevel
	})

	var infoWs []zapcore.WriteSyncer
	var warnWs []zapcore.WriteSyncer

	// 是否开启输出文件
	if l.Config.fileOut != nil {
		info, warn := enableFileOut(l.getFileWriter())
		infoWs = append(infoWs, info)
		warnWs = append(warnWs, warn)
	}

	// 控制台输出
	if l.Config.consoleOut {
		info, warn := enableConsoleOut()
		infoWs = append(infoWs, info)
		warnWs = append(warnWs, warn)
	}

	// 不能同时关闭两种输出方式，否则将开启控制台输出
	if l.Config.fileOut == nil && !l.Config.consoleOut {
		l.Warn("You cannot turn off both output modes at the same time!!!")
		info, warn := enableConsoleOut()
		infoWs = append(infoWs, info)
		warnWs = append(warnWs, warn)
	}

	wsInfo := zapcore.NewMultiWriteSyncer(infoWs...)
	wsWarn := zapcore.NewMultiWriteSyncer(warnWs...)
	// 日志是输出终端
	if isDebug {
		consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
		allCore = append(allCore, zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel))
	}

	if kafkaSwitch { // 日志输出kafka
		// kafka连接
		var kl LogKafka
		kl.Topic = config.C.Service.Name // Topic(话题)：Kafka中用于区分不同类别信息的类别名称。由producer指定
		kl.Partition = 1                 // Partition(分区)：Topic物理上的分组，一个topic可以分为多个partition，每个partition是一个有序的队列。partition中的每条消息都会被分配一个有序的id（offset）
		kl.Producer, err = kafka.GetkafkaProduct()
		if err != nil {
			panic(fmt.Sprintf("connect kafka failed: %+v\n", err))
		}

		writeSyncer := zapcore.AddSync(&kl)
		allCore = append(allCore, zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel))
	} else { // 日志输出file
		writeSyncer := getLumberJackWriter()
		allCore = append(allCore, zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel))
	}
	allCore = append(allCore, zapcore.NewCore(encoder, wsInfo, infoLevel))
	allCore = append(allCore, zapcore.NewCore(encoder, wsWarn, warnLevel))

	core := zapcore.NewTee(allCore...)

	// 开启开发模式，堆栈跟踪 需要传入 zap.AddCaller() 才会显示打日志点的文件名和行数, 有点小坑
	caller := zap.AddCaller()
	// 防止zap始终将包装器代码报告为调用者( 需要跳过一个级别，否则打印的文件名和行号 是封装的文件名)
	skip := zap.AddCallerSkip(l.Config.skip)

	zapLog := zap.New(core, caller, skip)
	//var cfg = zapsentry.Configuration{
	//
	//	Level: zapcore.ErrorLevel, // 发送日志级别
	//
	//	EnableBreadcrumbs: true,
	//
	//	BreadcrumbLevel: zapcore.ErrorLevel,
	//}
	//
	//sentryDsn := config.C.Sentry // DSN：sentry创建golang项目会提供
	//
	//core, err := zapsentry.NewCore(cfg, zapsentry.NewSentryClientFromDSN(sentryDsn))

	//zapLog = zapLog.With(zapsentry.NewScope())

	//if err != nil {
	//
	//	zapLog.Warn("failed to init zap", zap.Error(err))
	//
	//}
	defer zapLog.Sync()
	//l.zapSugar = zapsentry.AttachCoreToLogger(core, zapLog).Sugar()
	l.zapSugar = zapLog.Sugar().With()
}

func getLumberJackWriter() zapcore.WriteSyncer {
	lumberJackLogger := &lumberjack.Logger{
		Filename:   "./test.log", // 日志文件位置
		MaxSize:    1,            // 进行切割之前，日志文件最大值(单位：MB)，默认100MB
		MaxBackups: 5,            // 保留旧文件的最大个数
		MaxAge:     1,            // 保留旧文件的最大天数
		Compress:   false,        // 是否压缩/归档旧文件
	}

	return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(lumberJackLogger))
}

// With adds a variadic number of fields to the logging context.
// see https://github.com/uber-go/zap/blob/v1.10.0/sugar.go#L91
func (l *Logger) With(args ...interface{}) *Logger {
	l.zapSugar = l.zapSugar.With(args...)
	return l
}

// Debug package sugar of zap
func (l *Logger) Debug(args ...interface{}) {
	l.zapSugar.Debug(args...)
	NewSlsLogFormPrint(zapcore.DebugLevel.String(), args...)
}

// Debugf package sugar of zap
func (l *Logger) Debugf(template string, args ...interface{}) {
	l.zapSugar.Debugf(template, args...)
	NewSlsLogFormPrintf(zapcore.DebugLevel.String(), template, args...)
}

// Info package sugar of zap
func (l *Logger) Info(args ...interface{}) {
	l.zapSugar.Info(args...)
	NewSlsLogFormPrint(zapcore.InfoLevel.String(), args...)
}

// Infof package sugar of zap
func (l *Logger) Infof(template string, args ...interface{}) {
	l.zapSugar.Infof(template, args...)
	NewSlsLogFormPrintf(zapcore.InfoLevel.String(), template, args...)
}

// Warn package sugar of zap
func (l *Logger) Warn(args ...interface{}) {
	l.zapSugar.Warn(args...)
	NewSlsLogFormPrint(zapcore.WarnLevel.String(), args...)
}

// Warnf package sugar of zap
func (l *Logger) Warnf(template string, args ...interface{}) {
	l.zapSugar.Warnf(template, args...)
	NewSlsLogFormPrintf(zapcore.WarnLevel.String(), template, args...)
}

// Error package sugar of zap
func (l *Logger) Error(args ...interface{}) {
	l.zapSugar.Error(args...)
	NewSlsLogFormPrint(zapcore.ErrorLevel.String(), args...)
}

// Errorf package sugar of zap
func (l *Logger) Errorf(template string, args ...interface{}) {
	l.zapSugar.Errorf(template, args...)
	NewSlsLogFormPrintf(zapcore.ErrorLevel.String(), template, args...)
}

// Fatal package sugar of zap
func (l *Logger) Fatal(args ...interface{}) {
	l.zapSugar.Fatal(args...)
	NewSlsLogFormPrint(zapcore.FatalLevel.String(), args...)
}

// Fatalf package sugar of zap
func (l *Logger) Fatalf(template string, args ...interface{}) {
	l.zapSugar.Fatalf(template, args...)
	NewSlsLogFormPrintf(zapcore.FatalLevel.String(), template, args...)
}

// Panic package sugar of zap
func (l *Logger) Panic(args ...interface{}) {
	l.zapSugar.Panic(args...)
	NewSlsLogFormPrint(zapcore.PanicLevel.String(), args...)
}

// Panicf package sugar of zap
func (l *Logger) Panicf(template string, args ...interface{}) {
	l.zapSugar.Panicf(template, args...)
	NewSlsLogFormPrintf(zapcore.PanicLevel.String(), template, args...)
}

func NewSlsLogFormPrint(logLevel string, args ...interface{}) {
	//logC := fmt.Sprint(args...)
	//nowTime := time.Now().Unix()
	////if err := LogPushSls.Submit(publiPutLog(logLevel, logC, nowTime)); err != nil {
	////	fmt.Println(err.Error())
	////}
	//publiPutLog(logLevel, logC, nowTime)
	return
}

func NewSlsLogFormPrintf(logLevel, template string, args ...interface{}) {
	//logC := fmt.Sprintf(template, args...)
	//nowTime := time.Now().Unix()
	////if err := LogPushSls.Submit(publiPutLog(logLevel, logC, nowTime)); err != nil {
	////	fmt.Println(err.Error())
	////}
	//publiPutLog(logLevel, logC, nowTime)
	return
}

//func publiPutLog(logLevel, logC string, nowTime int64) {
//	var slsLog slsLog.SlsLogFormt
//	slsLog.LogLevel = logLevel
//	var resMap map[string]any
//	json.Unmarshal([]byte(logC), &resMap)
//	if len(resMap) != 0 {
//		slsLog.LogContent = resMap
//	} else {
//		slsLog.LogContent = logC
//	}
//	slsLog.PrintLogToSls(nowTime)
//}
